#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>

#include "auxNodeEdge.h"
#include "../glib/glist.h"

#define print_NAprint(...) if(naprint) printf(__VA_ARGS__)

extern int naprint;

#define PRE_AN_NODE(x) ((x) != NULL && (x)->n != NULL)
#define PRE_AN_EDGE(x) ((x) != NULL && (x)->e != NULL && (x)->dst != NULL)

/********************************* Estructuras *******************************/

/* Nodo */
struct sNodeANet{
    node n;             /* Puntero al nodo en el network original */
    GList *neighbors;   /* Lista de punteros a sus vecinos hacia adelante */
};

/* Arista */
struct sEdgeANet{
    nodeANet dst;  /* Puntero al nodo de destino de la arista */
    edge e;        /* Arista en el network original. */
    bool forward;  /* Para saber si la arista original es Forward o Backward */
};

/************************************ Nodo ***********************************/

nodeANet nodeANet_create(const node n, const int level){
    nodeANet nANet = NULL;
    assert(n != NULL);
    assert(level >= -1);

    nANet = (nodeANet) malloc(sizeof(struct sNodeANet));
    if(nANet == NULL){
        errx(EXIT_FAILURE, "Memoria insuficiente");
    }

    /* Inicializo la estructura */
    nANet->n = n;
    nANet->neighbors = NULL;
    node_setLevel(n, level);

    assert(PRE_AN_NODE(nANet));

    return nANet;
}

nodeANet nodeANet_destroy(nodeANet nANet){
    assert(PRE_AN_NODE(nANet));

    /* Elimino la lista de vecinos */
    g_list_free_full(nANet->neighbors, (GDestroyNotify) edgeANet_destroy);
    nANet->neighbors = NULL;

    /* El nodo original no se elimina */
    nANet->n = NULL;

    free(nANet);
    nANet = NULL;

    return nANet;
}

node nodeANet_getNode(const nodeANet nANet){
    assert(PRE_AN_NODE(nANet));
    return nANet->n;
}

void nodeANet_addNeighbor(nodeANet nANet, const edgeANet e){
    assert(PRE_AN_NODE(nANet));
    /* Agrego la arista a la lista de vecinos */
    nANet->neighbors = g_list_prepend(nANet->neighbors, (gpointer) e);
}

void nodeANet_delNeighbor(nodeANet nANet, edgeANet e){
    assert(PRE_AN_NODE(nANet));
    /* Quito la arista de la lista de vecinos */
    nANet->neighbors = g_list_remove(nANet->neighbors, e);
}

nodeANet nodeANet_getNeighbor(const nodeANet nANet, const unsigned int label){
    unsigned int i = 0, length = 0;
    nodeANet y = NULL;
    GList *piv = NULL;

    assert(PRE_AN_NODE(nANet));

    /* 'piv' se mueve por la lista de vecinos buscando el nodo */
    piv = nANet->neighbors;
    length = g_list_length(piv);

    for (i = 0; i < length; i++){
        /* Obtengo un vecino de la lista */
        y = (nodeANet) edgeANet_getNeighbor(g_list_data(piv));
        /* Si es el que estaba buscando, lo devuelvo */
        if (nodeANet_getLabel(y) == label){
            return y;
        }
        /* Avanzo hacia otro elemento de la lista */
        piv = g_list_next(piv);
    }
    /* No se lo encontró */
    return NULL;
}

GList *nodeANet_getNeighbors(const nodeANet nANet){
    assert(PRE_AN_NODE(nANet));
    return nANet->neighbors;
}

void nodeANet_setNeighbors(nodeANet nANet, GList *neighbors){
    assert(PRE_AN_NODE(nANet));
    nANet->neighbors = neighbors;
}

unsigned int nodeANet_getLabel(const nodeANet nANet){
    assert(PRE_AN_NODE(nANet));
    return node_getLabel(nANet->n);
}

int nodeANet_getLevel(const nodeANet nANet){
    assert(PRE_AN_NODE(nANet));
    return node_getLevel(nANet->n);
}

void nodeANet_print(const nodeANet nANet){
    edgeANet e = NULL;
    unsigned int length = 0, i = 0;
    GList *piv = NULL;

    assert(PRE_AN_NODE(nANet));

    length = g_list_length(nANet->neighbors);
    piv =  nANet->neighbors;
    print_NAprint("|(%u)", nodeANet_getLabel(nANet));
    for (i = 0; i < length; i++){
        e = (edgeANet) g_list_data(piv);
        edgeANet_print(e);
        piv = g_list_next(piv);
    }
    print_NAprint("\n");
}

/*********************************** Arista **********************************/

edgeANet edgeANet_create(const edge e, const nodeANet dst, const bool forward){
    edgeANet eANet = NULL;

    eANet = (edgeANet) malloc(sizeof(struct sEdgeANet));
    if(eANet == NULL){
        errx(EXIT_FAILURE, "Memoria insuficiente");
    }
    /* Inicializo la estructura */
    eANet->dst = dst;
    eANet->e = e;
    eANet->forward = forward;

    assert(PRE_AN_EDGE(eANet));

    return eANet;
}

edgeANet edgeANet_destroy(edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));

    /* La arista original y el nodo de destino no se eliminan aquí */
    eANet->e = NULL;
    eANet->dst = NULL;

    free(eANet);
    eANet = NULL;

    return eANet;
}

edge edgeANet_getEdge(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return eANet->e;
}

unsigned int edgeANet_getCap(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return edge_getCap(eANet->e);
}

unsigned int edgeANet_getFlow(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return edge_getFlow(eANet->e);
}

void edgeANet_increaseFlow(edgeANet eANet, const unsigned int flow){
    assert(PRE_AN_EDGE(eANet));
    edge_increaseFlow(eANet->e, flow);
}

void edgeANet_decreaseFlow(edgeANet eANet, const unsigned int flow){
    assert(PRE_AN_EDGE(eANet));
    edge_decreaseFlow(eANet->e, flow);
}

bool edgeANet_isSaturated(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return edge_isSaturated(eANet->e);
}

bool edgeANet_isEmpty(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return edge_isEmpty(eANet->e);
}

nodeANet edgeANet_getNeighbor(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return eANet->dst;
}

bool edgeANet_isForwardInNetwork(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    return eANet->forward;
}

void edgeANet_print(const edgeANet eANet){
    assert(PRE_AN_EDGE(eANet));
    print_NAprint("-->(%u) flow[%u] cap(%u)\n|   ",
                         nodeANet_getLabel(eANet->dst),
                         edgeANet_getFlow(eANet), edgeANet_getCap(eANet));
}

